#include <buola/cv/barcode/decode.h>
#include <numeric>

namespace buola { namespace cvi { namespace barcode {

///get the width of a bar (or sum of bars) with respect to the base bar width)
///
///\param pW the width to measure
///\param p700BarW 700 times the base bar width
///
///\return the rounded width of \p pW in bar units
static inline int ean_get_width(int pW,int p700BarW)
{
    return (700*pW+p700BarW/2)/p700BarW;
}

///conversion of width pairs to digits
///
///For ambiguous digits, the lowest possible value is selected.
///
///16 is added for 'G' digits.
static const int sWidthsToDigits[16]=
{
    6,  25, 9,  22, 16, 2,  18, 0,  4,  17, 1,  20, 19, 5,  21, 3
};

static inline int ean_get_digit(const int *pPtr,int p700BarW)
{
    int lA=ean_get_width(pPtr[0]+pPtr[1],p700BarW)-2;
    if(lA==-1) lA=0;
    if(lA<0||lA>3) return -1;
    int lB=ean_get_width(pPtr[1]+pPtr[2],p700BarW)-2;
    if(lB==-1) lB=0;
    if(lB<0||lB>3) return -1;

    int lDigit=sWidthsToDigits[lA+(lB<<2)];
    if(lDigit==1||lDigit==2) //it might be a 7 or an 8
    {
        if(ean_get_width(100*(pPtr[0]+pPtr[2]),p700BarW)<300)
            lDigit+=6;
    }
    else if(lDigit==17||lDigit==18) //it might be a 23 or a 24
    {
        if(ean_get_width(100*(pPtr[0]+pPtr[2]),p700BarW)>400)
            lDigit+=6;
    }
    
    return lDigit;
}

///check that a left mark starts at \p pPtr (going backwards)
static inline bool ean_check_mark_left(const int *pPtr,int p700BarW)
{
    if(ean_get_width((pPtr[0]+2*pPtr[-1]+pPtr[-2])/2,p700BarW)!=2) return false;
//    if(ean_get_width(pPtr[-3],p700BarW)<3) return false;
    return true;
}

static inline bool ean_check_mark_middle(const int *pPtr,int p700BarW)
{
    if(ean_get_width((2*pPtr[0]+3*pPtr[1]+2*pPtr[2]+3*pPtr[3]+2*pPtr[4])/6,p700BarW)!=2) return false;
    return true;
}

static inline bool ean_check_mark_right(const int *pPtr,int p700BarW)
{
    if(ean_get_width((pPtr[0]+2*pPtr[1]+pPtr[2])/2,p700BarW)!=2) return false;
//    if(ean_get_width(pPtr[3],p700BarW)<3) return false;
    return true;
}

static inline int calc_leading_digit(int *pSyms,int pN,bool &pReversed)
{
    int lLead=0;
    for(int i=0;i<pN;i++)
    {
        lLead=(lLead<<1)|(pSyms[i]>>4);
    }

    if(lLead&0x3f) //it's reverted... swap digits
    {
        std::reverse(pSyms,pSyms+pN);
        lLead=0;
        for(int i=0;i<pN;i++)
        {
            lLead=(lLead<<1)|(pSyms[i]>>4);
        }
        lLead^=0xfff;
        pReversed=true;
    }
    else
    {
        pReversed=false;
    }
    
    switch(lLead)
    {
    case 0x000: return 0;
    case 0x2c0: return 1;
    case 0x340: return 2;
    case 0x380: return 3;
    case 0x4c0: return 4;
    case 0x640: return 5;
    case 0x700: return 6;
    case 0x540: return 7;
    case 0x580: return 8;
    case 0x680: return 9;
    default:    return -1;
    }
}

static inline bool ean_checksum(int *pSyms,int pN)
{
    int lWeight=1;
    int lSum=0;
    for(int i=0;i<pN;i++)
    {
        lSum+=lWeight*(pSyms[i]&0xf);
        lWeight=4-lWeight;
    }
    
    return (lSum%10==0);
}

static inline std::string syms_to_text(int *pSyms,int pN)
{
    std::string lString(pN,' ');
    for(int i=0;i<pN;i++)
    {
        lString[i]='0'+(pSyms[i]&0xf);
    }
    
    return lString;
}

void decode_ean(const std::vector<int> &pWidths,std::vector<CSymbol> &pSymbols)
{
    int lSyms[13];
    
    const int *lBegin=&pWidths.front();
    const int *lEnd=&pWidths.back();

    for(const int *lPtr=lBegin+4;lPtr<lEnd-3;lPtr+=2)
    {
        int l700BarW=100*(lPtr[0]+lPtr[1]+lPtr[2]+lPtr[3]);
        if(!ean_check_mark_left(lPtr-1,l700BarW)) continue;

        int j=1;
        const int *lTmp;
        for(lTmp=lPtr;lTmp<lEnd-3&&j<7;lTmp+=4)
        {
            int lTmp700BarW=100*(lTmp[0]+lTmp[1]+lTmp[2]+lTmp[3]);
            lSyms[j]=ean_get_digit(lTmp,lTmp700BarW);
            if(lSyms[j]<0) break;
            j++;
            l700BarW=lTmp700BarW;
        }
        
        if(j!=7) continue;
        if(lTmp+4>=lEnd) continue;
        if(!ean_check_mark_middle(lTmp,l700BarW)) continue;
     
        for(lTmp=lTmp+5;lTmp<lEnd-3&&j<13;lTmp+=4)
        {
            int lTmp700BarW=100*(lTmp[0]+lTmp[1]+lTmp[2]+lTmp[3]);
            lSyms[j]=ean_get_digit(lTmp,lTmp700BarW);
            if(lSyms[j]<0) break;
            j++;
            l700BarW=lTmp700BarW;
        }
        if(j!=13) continue;
        if(lTmp+2>=lEnd) continue;
        if(!ean_check_mark_right(lTmp,l700BarW)) continue;

        bool lReversed;
        lSyms[0]=calc_leading_digit(lSyms+1,j-1,lReversed);
        if(lSyms[0]<0) continue;
        
        if(!ean_checksum(lSyms,j)) continue;
        
        CSymbol lSym;
        lSym.mB=std::accumulate(lBegin,lPtr-3,0)>>5;
        lSym.mE=lSym.mB+(std::accumulate(lPtr-3,lTmp+3,0)>>5);
        lSym.mText=syms_to_text(lSyms,j);
        lSym.mType=EType::EAN;
        lSym.mDirection=lReversed?-1:1;
        
        pSymbols.push_back(lSym);
        lPtr=lTmp+3;
    }
}
    
/*namespace barcode*/ } /*namespace cvi*/ } /*namespace buola*/ }
